# pytorch的基本操作——数据创建与运算

# 导入模块
import torch
import numpy as np

# 添加下面这个语句可以让jupyter输出多行
# from IPython.core.interactiveshell import InteractiveShell
# InteractiveShell.ast_node_interactivity = "all"

# tensor数据的创建
# "tensor"这个单词一般可译作“张量”，张量可以看作是一个多维数组。标量可以看作是0维张量，向量可以看作1维张量，矩阵可以看作是二维张量
# 创建一个未初始化5*3的tensor
print('未初始化的5*3的tensor')
x = torch.empty(5, 3)
# print(x)
x

# 创建要给随机的5*3的tensor,rand表示数据符合均匀分布，randn表示数据符合正态分布
x = torch.rand(5, 3)
print('随机的5*3的tensor')
print(x)

# 创建一个long型全0的tensor
torch.zeros(5, 3, dtype=torch.long)

# 根据数据直接创建
torch.tensor([[2, 3], [3, 4]])

# 创建全1tensor
torch.ones(5, 3, dtype=torch.float64)

# 创建单位矩阵
torch.eye(5, 5, dtype=torch.float)

# 根据已有tensor创建新tensor
x.new_ones(5, 3, dtype=torch.float64)

# 创建一个和x形状相同的tensor，元素大小随机
torch.randn_like(x, dtype=torch.float)

# tensor的形状和大小
print(x.shape)
print(x.size())

# tensor数据的操作
y = torch.rand(5, 3)
print('x:')
print(x)
print('y:')
print(y)

# 两个tensor相加，大小相同，对应位置相加；大小不同，不能直接相加
# 形式1
print('x+y:')
print(x + y)
# 形式2
print('add(x+y):')
print(torch.add(x, y))
# 添加参数可指定结果，out=result，结果存储到res变量中
# torch.add(x, y, out=result)
# 形式3
# y.add_(x)

# 索引
# 第0行数据
print('x[0,:]')
print(x[0, :])

# 高级索引
eye = torch.eye(5, 5)
print('eye:')
print(eye)
# 获取非0元素下标
print('非0：')
torch.nonzero(eye)

# 改变大小，变1*15
y = x.view(15)
print(y)
# -1表示维度根据后面的参数5推出来，变3*5
z = x.view(-1, 5)
print(z)
# 修改大小之后返回的tensor和原来的tensor是共享数据data的，改变x，y也会变，view仅仅是改变了对这个张量的观察角度，内部数据并未改变
# 但是依然是一个新的Tensor（因为Tensor除了包含data外还有一些其他属性），二者id（内存地址）并不一致。
# 每个元素都加1
print('x+1:')
x += 1
print(x)
print('y:')
print(y)
# 如果不想共享数据data，则可以进行深复制
x_cp = x.clone().view(15)
print('x_cp:')
print(x_cp)
x += 1
print('x+1:')
print(x)
print('x_cp:')
print(x_cp)

# item可以将一个tensor标量转化为Python number
# x = torch.rand(1)
# print('tensor标量')
# x
# print('Python number')
# x.item()

# 线性代数
x = torch.rand(5, 5)
print(x)
# 矩阵的迹
print(x.trace())
# 对角线元素
print(x.diag())
# 矩阵转置
print(x.t())
# 矩阵的逆
print(x.inverse())

# pytorch的基本操作——广播机制

# 当对两个形状不同的Tensor按元素运算时，可能会触发广播（broadcasting）机制：先适当复制元素使这两个Tensor形状相同后再按元素运算。
# 可广播的张量满足以下规则：
# 1、每个tensor至少有一个维度。2、迭代维度尺寸时，从尾部的维度开始，维度尺寸或者相等，或者其中一个张量的维度尺寸为 1 ，或者其中一个张量不存在这个维度。

# 定义两个tensor，arange左闭右开
x = torch.arange(1, 3).view(1, 2)
print('x:')
print(x)
y = torch.arange(1, 4).view(3, 1)
print('y:')
print(y)

# 如果要计算x + y，那么x中第一行的2个元素被广播（复制）到了第二行和第三行，而y中第一列的3个元素被广播（复制）到了第二列。如此，就可以对2个3行2列的矩阵按元素相加
print(x + y)

# 开辟新地址
x = torch.tensor([1, 2])
y = torch.tensor([3, 4])
# y的内存地址
id_before = id(y)
print(id_before)
# 开辟了一个新的地址，然后将y指向新的地址
y = y + x
print(y)
print(id(y) == id_before)

# 不开辟新地址
x = torch.tensor([1, 2])
y = torch.tensor([3, 4])
# y的内存地址
id_before = id(y)
print(id_before)
# 以下几种方式，均不开辟新的地址，而是使用y原来的地址
y[:] = y + x
# y += x
# y.add_(x)
# torch.add(x, y, out=y)
print(y)
print(id(y) == id_before)

# Tensor转numpy 用numpy()和from_numpy()将Tensor和NumPy中的数组相互转换。但是需要注意的一点是：
# 这两个函数所产生的的Tensor和NumPy中的数组共享相同的内存（所以他们之间的转换很快），改变其中一个时另一个也会改变！！！ 生成一个一维全1序列
a = torch.ones(5)
b = a.numpy()
print(a, b)
a += 1
print(a, b)
b += 1
print(a, b)

# numpy转Tensor
a = np.ones(5)
b = torch.from_numpy(a)
print(a, b)
a += 1
print(a, b)
b += 1
print(a, b)

# 还有一个常用的将NumPy中的array转换成Tensor的方法就是torch.tensor(), 需要注意的是，此方法总是会进行数据拷贝（就会消耗更多的时间和空间），所以返回的Tensor和原来的数据不再共享内存。
# 生成一个一维全1序列
a = np.ones(5)
b = torch.tensor(a)
print(a, b)
a += 1
print(a, b)
b += 1
print(a, b)
